﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Windows;

namespace GongZhu
{
    public class Server
    {
        #region fields
            private string roomName;
            private int port;
            private UdpClient udpServer;
            Thread thread;
            private Card[] orginalCards;
            private Card[] cards;
            private Card[][] dealedCards;
            private User[] users;
            private int userCount = 0;
        #endregion
        public Server(string roomName, int port)
        {
            this.roomName = roomName;
            this.port = port;
            udpServer = new UdpClient(port);
            thread = new Thread(Dispatch);
            thread.IsBackground = true;
            thread.Start();
            users = new User[4];
            cards = new Card[52];
            dealedCards = new Card[4][];
            dealedCards[0] = new Card[13];
            dealedCards[1] = new Card[13];
            dealedCards[2] = new Card[13];
            dealedCards[3] = new Card[13];
        }

        public void Quit()
        {
            
            thread.Abort();
            udpServer.Close();
            udpServer = null;
        }
        private void Dispatch()
        {
            #region initialize cards
            orginalCards = new Card[52];

            //Heart
            orginalCards[0] = new Card(CardType.Heart,CardNumberType.A);
            orginalCards[1] = new Card(CardType.Heart, CardNumberType.Eight);
            orginalCards[2] = new Card(CardType.Heart, CardNumberType.Five);
            orginalCards[3] = new Card(CardType.Heart, CardNumberType.Four);
            orginalCards[4] = new Card(CardType.Heart, CardNumberType.Jack);
            orginalCards[5] = new Card(CardType.Heart, CardNumberType.King);
            orginalCards[6] = new Card(CardType.Heart, CardNumberType.Nine);
            orginalCards[7] = new Card(CardType.Heart, CardNumberType.Queen);
            orginalCards[8] = new Card(CardType.Heart, CardNumberType.Seven);
            orginalCards[9] = new Card(CardType.Heart, CardNumberType.Six);
            orginalCards[10] = new Card(CardType.Heart, CardNumberType.Ten);
            orginalCards[11] = new Card(CardType.Heart, CardNumberType.Three);
            orginalCards[12] = new Card(CardType.Heart, CardNumberType.Two);


            //Club
            orginalCards[13] = new Card(CardType.Club, CardNumberType.A);
            orginalCards[14] = new Card(CardType.Club, CardNumberType.Eight);
            orginalCards[15] = new Card(CardType.Club, CardNumberType.Five);
            orginalCards[16] = new Card(CardType.Club, CardNumberType.Four);
            orginalCards[17] = new Card(CardType.Club, CardNumberType.Jack);
            orginalCards[18] = new Card(CardType.Club, CardNumberType.King);
            orginalCards[19] = new Card(CardType.Club, CardNumberType.Nine);
            orginalCards[20] = new Card(CardType.Club, CardNumberType.Queen);
            orginalCards[21] = new Card(CardType.Club, CardNumberType.Seven);
            orginalCards[22] = new Card(CardType.Club, CardNumberType.Six);
            orginalCards[23] = new Card(CardType.Club, CardNumberType.Ten);
            orginalCards[24] = new Card(CardType.Club, CardNumberType.Three);
            orginalCards[25] = new Card(CardType.Club, CardNumberType.Two);

            //Spade
            orginalCards[26] = new Card(CardType.Spade, CardNumberType.A);
            orginalCards[27] = new Card(CardType.Spade, CardNumberType.Eight);
            orginalCards[28] = new Card(CardType.Spade, CardNumberType.Five);
            orginalCards[29] = new Card(CardType.Spade, CardNumberType.Four);
            orginalCards[30] = new Card(CardType.Spade, CardNumberType.Jack);
            orginalCards[31] = new Card(CardType.Spade, CardNumberType.King);
            orginalCards[32] = new Card(CardType.Spade, CardNumberType.Nine);
            orginalCards[33] = new Card(CardType.Spade, CardNumberType.Queen);
            orginalCards[34] = new Card(CardType.Spade, CardNumberType.Seven);
            orginalCards[35] = new Card(CardType.Spade, CardNumberType.Six);
            orginalCards[36] = new Card(CardType.Spade, CardNumberType.Ten);
            orginalCards[37] = new Card(CardType.Spade, CardNumberType.Three);
            orginalCards[38] = new Card(CardType.Spade, CardNumberType.Two);

            //Diamond
            orginalCards[39] = new Card(CardType.Diamond, CardNumberType.A);
            orginalCards[40] = new Card(CardType.Diamond, CardNumberType.Eight);
            orginalCards[41] = new Card(CardType.Diamond, CardNumberType.Five);
            orginalCards[42] = new Card(CardType.Diamond, CardNumberType.Four);
            orginalCards[43] = new Card(CardType.Diamond, CardNumberType.Jack);
            orginalCards[44] = new Card(CardType.Diamond, CardNumberType.King);
            orginalCards[45] = new Card(CardType.Diamond, CardNumberType.Nine);
            orginalCards[46] = new Card(CardType.Diamond, CardNumberType.Queen);
            orginalCards[47] = new Card(CardType.Diamond, CardNumberType.Seven);
            orginalCards[48] = new Card(CardType.Diamond, CardNumberType.Six);
            orginalCards[49] = new Card(CardType.Diamond, CardNumberType.Ten);
            orginalCards[50] = new Card(CardType.Diamond, CardNumberType.Three);
            orginalCards[51] = new Card(CardType.Diamond, CardNumberType.Two);

            #endregion
            
            byte[] buffer;
            IPEndPoint ip = null;
            while (true)
            {
                buffer = udpServer.Receive(ref ip);
                Message msg = Message.FromBytes(buffer);
                switch (msg.MessageType)
                {
                    case MessageType.RoomIn://
                        //check if the room is full
                        if (userCount == 4)
                        {
                            Message roomInMsg = new Message();
                            roomInMsg.MessageType = MessageType.RoomFull;
                            roomInMsg.Data = "Sorry that the room " + roomName + " is full, please select other room.";
                            byte[] roomInMsgBytes = roomInMsg.ToBytes();
                            udpServer.Send(roomInMsgBytes, roomInMsgBytes.Length, ip);

                        }
                        else
                        {
                            //check if the username exists                                                  
                            if (IsDuplicatedName(msg.UserName))
                            {
                                Message nameDuplicateMsg = new Message();
                                nameDuplicateMsg.UserName = msg.UserName;
                                nameDuplicateMsg.MessageType = MessageType.NameDuplicate;
                                nameDuplicateMsg.Data = "Name " + msg.UserName + " has existed, please change other username";
                                byte[] nameduplicateMsgBytes = nameDuplicateMsg.ToBytes();
                                udpServer.Send(nameduplicateMsgBytes, nameduplicateMsgBytes.Length, ip);
                                break;
                            }
                            else
                            {
                                users[userCount] = new User();
                                users[userCount].Index = userCount;
                                users[userCount].UserName = msg.UserName;
                                users[userCount].Port = ip.Port;
                                users[userCount].RealName = msg.UserName + DateTime.Now.Ticks;
                                users[userCount].IP = ip;

                                Message roomInMsg = new Message();
                                roomInMsg.MessageType = MessageType.RoomIn;
                                roomInMsg.UserName = msg.UserName;
                                //roomInMsg.Data = msg.UserName + " join us. |" + roomName;
                                roomInMsg.Data = SerializeHelper.ToBytes<User[]>(users);
                                roomInMsg.RealName = users[userCount].RealName;
                                byte[] roomInMsgBytes = roomInMsg.ToBytes();
                                for (int i = 0; i < userCount + 1; i++)
                                {
                                    udpServer.Send(roomInMsgBytes, roomInMsgBytes.Length, users[i].IP);
                                }

                                userCount++;

                                //MessageBox.Show(msg.UserName + "   " + userCount); done
                                if (userCount == 4)//start game
                                {
                                    StartGame();
                                }
                            }
                        }
                        break;
                    case MessageType.RoomOut://now don't implement

                        break;
                    case MessageType.RoomInfo://server will return the roomname and the user count,seperated by |
                        Message roomInfoMsg = new Message();
                        roomInfoMsg.MessageType = MessageType.RoomInfo;
                        roomInfoMsg.UserName = "";
                        roomInfoMsg.Data = roomName+"|"+userCount;
                        byte[] roomInfoMsgBytes = roomInfoMsg.ToBytes();
                        udpServer.Send(roomInfoMsgBytes, roomInfoMsgBytes.Length,ip);
                        break;
                    case MessageType.RoomFull:// server never receive this message
                        break;
                    case MessageType.Rename:// server will send Rename message to the other users to modify the display

                        if (IsDuplicatedName(msg.UserName))
                        {
                            Message nameDuplicateMsg = new Message();
                            nameDuplicateMsg.UserName = msg.UserName;
                            nameDuplicateMsg.MessageType = MessageType.NameDuplicate;
                            nameDuplicateMsg.Data = "Name "+msg.UserName+" has existed, please change other username";
                            byte[] nameduplicateMsgBytes = nameDuplicateMsg.ToBytes();
                            MessageBox.Show((string)nameDuplicateMsg.Data);
                            udpServer.Send(nameduplicateMsgBytes, nameduplicateMsgBytes.Length,ip);
                            break;
                        }
                        else 
                        { 
                            User user = FindUser(msg.RealName);
                            if (user != null)
                            {
                                Message renameMsg = new Message();
                                renameMsg.MessageType = MessageType.Rename;
                                renameMsg.UserName = msg.UserName;
                                renameMsg.RealName = user.RealName;
                                renameMsg.Data = user.UserName + " rename to " + msg.UserName;
                                user.UserName = msg.UserName;

                                byte[] renameMsgBytes = renameMsg.ToBytes();
                                for (int i = 0; i < userCount; i++)
                                {
                                    udpServer.Send(renameMsgBytes, renameMsgBytes.Length, users[i].IP);
                                }
                            }
                        }
                        
                        break;
                    case MessageType.PlayCard:// server will send PlayCard to the other users
                        
                        break;
                    case MessageType.ShowCard:
                        MessageBox.Show("show card server!");
                        break;
                    default:
                        break;
                }
            }
        }

        private bool IsDuplicatedName(string username)
        {
            for (int i = 0; i < userCount; i++)
            {
                if (username == users[i].UserName)
                {
                    return true;
                }
            }
            return false;
        }

        private User FindUser(string realName)
        {
            for (int i = 0; i < userCount; i++)
            {
                if (users[i].RealName == realName)
                    return users[i];
            }

            return null; 
        }

        private void StartGame()
        {
            ShuffleCard();
            DealCard();
            SendCard();
        }

        private void ShuffleCard()
        {
            //cards
            List<Card> cardList = new List<Card>(orginalCards);
            Random rdm = new Random((int)DateTime.Now.Ticks);
            int index;
            for(int i = 51; i >-1; i--)
            {
                index = rdm.Next(0, i+1);
                cards[i] = cardList[index];
                cardList.RemoveAt(index);
            }
        }

        private void DealCard()
        {
            List<Card> cardList = new List<Card>(cards);
            Random rdm = new Random((int)DateTime.Now.Ticks);
            int[] cardCount = new int[4];
            cardCount[0] = 0;
            cardCount[1] = 0;
            cardCount[2] = 0;
            cardCount[3] = 0;

            int index;
            for (int i = 51; i > -1; i--)
            {
                index = rdm.Next(0, i + 1);
                dealedCards[i % 4][cardCount[i % 4]] = cardList[index];
                cardCount[i % 4]++;
                cardList.RemoveAt(index);
            }
        }

        private void SendCard()
        {
            Message msg = new Message();
            msg.MessageType = MessageType.DealCard;
            
            for (int i = 0; i < 4; i++)
            {
                msg.RealName = users[i].RealName;
                msg.UserName = users[i].UserName;
                msg.Data = SerializeHelper.ToBytes<Card[]>(dealedCards[i]);

                byte[] bytes = msg.ToBytes();
                udpServer.Send(bytes, bytes.Length, users[i].IP);
            }
        }
    }

    [Serializable]
    public class Message
    {
        private MessageType messageType;
        private string userName;
        private object data;
        private string realName;

        public MessageType MessageType
        {
            set { this.messageType = value; }
            get { return messageType; }
        }

        public string UserName
        {
            set { userName = value; }
            get { return userName; }
        }

        public object Data
        {
            set { this.data = value; }
            get { return this.data; }
        }

        public string RealName
        {
            set { realName = value; }
            get { return realName; }
        }

        public byte[] ToBytes()
        {
            return SerializeHelper.ToBytes<Message>(this);
        }

        public static Message FromBytes(byte[] bytes)
        {
            Message msg = SerializeHelper.FromBytes<Message>(bytes);
            return msg;
        }
    }

    public enum MessageType
    {
        RoomIn,
        RoomOut,
        Rename,
        RoomFull,
        RoomInfo,
        PlayCard,
        NameDuplicate,
        DealCard,
        ShowCard,
        PlayStart
    }

    [Serializable]
    public class User
    {
        private string userName;
        private int port;
        private int index;
        private string realName;
        private IPEndPoint ip;
        public string UserName
        {
            set { userName = value; }
            get { return userName; }
        }

        public int Port
        {
            set { port = value; }
            get { return port; }
        }

        public int Index
        {
            get { return index; }
            set { index = value; }
        }

        public string RealName
        {
            get { return realName; }
            set { realName = value; }
        }

        public IPEndPoint IP
        {
            get { return ip; }
            set { ip = value; }
        }

        public byte[] ToBytes()
        {
            return SerializeHelper.ToBytes<User>(this);
        }

        public static User FromBytes(byte[] bytes)
        {
            return SerializeHelper.FromBytes<User>(bytes);
        }

        public override bool Equals(object obj)
        {
            //return base.Equals(obj);
            User user = obj as User;
            return user.realName == this.realName;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    public enum CardType
    {
        Heart,
        Spade,
        Club,
        Diamond
    }

    public enum CardNumberType
    {
        Two,
        Three,
        Four,
        Five,
        Six,
        Seven,
        Eight,
        Nine,
        Ten,
        Jack,
        Queen,
        King,
        A
    }

    [Serializable]
    public class Card : IComparable<Card>
    {
        private CardType cardType;
        private CardNumberType cardNumberType;

        public CardType CardType
        {
            set { cardType = value; }
            get { return cardType; }
        }

        public CardNumberType CardNumberType
        {
            set { cardNumberType = value; }
            get { return cardNumberType; }
        }

        public Card(CardType ct, CardNumberType cnt)
        {
            this.cardType = ct;
            this.cardNumberType = cnt;
        }

        public override int GetHashCode()
        {
            return ((int)cardType) * 100 + ((int)cardNumberType);
        }

        public override bool Equals(object obj)
        {
            Card card = obj as Card;
            return cardType == card.cardType && cardNumberType == card.cardNumberType;
        }

        public int CompareTo(Card other)
        {
            return GetHashCode() - other.GetHashCode();
        }

        private static bool IsScoreCard(Card card)
        {
            if (card.CardType == CardType.Heart)
                return true;

            if (card.CardType == CardType.Spade && card.CardNumberType == CardNumberType.Ten)
                return true;

            if(card.cardType == CardType.Diamond && card.CardNumberType == CardNumberType.Jack)
                return true;

            if (card.CardType == CardType.Club && card.CardNumberType == CardNumberType.Queen)
                return true;

            return false;
        }

        public bool IsScoredCard()
        {
            return IsScoreCard(this);
        }


        private static bool CanShow(Card card)
        {
            if ((card.CardType == CardType.Heart && card.CardNumberType == CardNumberType.A) ||
                (card.CardType == CardType.Club && card.CardNumberType == CardNumberType.Queen) ||
                (card.CardType == CardType.Diamond && card.CardNumberType == CardNumberType.Jack) ||
                (card.CardType == CardType.Spade && card.CardNumberType == CardNumberType.Ten))
                return true;
            return false;
        }

        public bool CanShow()
        {
            return CanShow(this);
        }
        public override string ToString()
        {
            return CardType.ToString() + "_" +CardNumberType.ToString();
        }

        public static Card FromString(string str)
        {
            string[] strs = str.Split('_');
            string ctstr = strs[0];
            string cntstr = strs[1];
            CardType ct = (CardType)Enum.Parse(typeof(CardType), ctstr);
            CardNumberType cnt = (CardNumberType)Enum.Parse(typeof(CardNumberType), cntstr);

            return new Card(ct,cnt);
        }

        public static Card FromBytes(byte[] bytes)
        {
            return SerializeHelper.FromBytes<Card>(bytes);
        }

        public byte[] ToBytes()
        {
            return SerializeHelper.ToBytes<Card>(this);
        }
    }

    [Serializable]
    public class CardHistory
    {
        private int round;
        private Card card;
        private User user;

        public int Round
        {
            get { return round; }
            set { round = value; }
        }

        public Card Card
        {
            get { return card; }
            set { card = value; }
        }

        public User User
        {
            get { return user; }
            set { user = value; }
        }

    }

    [Serializable]
    public class ScoreCard
    {
        private Card card;
        private User user;

        public Card Card
        {
            get { return card; }
            set { card = value; }
        }

        public User User
        {
            get { return user; }
            set { user = value; }
        }
    }

    #region SerializeHelper
    public class SerializeHelper
    {
        private static BinaryFormatter bf;
        static SerializeHelper()
        {
            bf = new BinaryFormatter();
        }

        public static byte[] ToBytes<T>(T obj)
        {
            MemoryStream ms = new MemoryStream();

            bf.Serialize(ms, obj);
            return ms.GetBuffer();
        }

        public static T FromBytes<T>(byte[] bytes)
        {
            MemoryStream ms = new MemoryStream(bytes);
            return (T)bf.Deserialize(ms);
        }
    }
    #endregion
}
